//==================================================================
//  Copyright (C) 2006  Davide Pasca
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//==================================================================
//==
//==
//==
//==
//===============================================================================

#include "stdafx.h"
#include "shcube.h"

enum {
	CUBECROSS_FACE_NX=0,		// LEFT
	CUBECROSS_FACE_PX,			// RIGHT
	CUBECROSS_FACE_NY,			// BOTTOM
	CUBECROSS_FACE_PY,			// TOP
	CUBECROSS_FACE_NZ,			// BACK
	CUBECROSS_FACE_PZ			// FRONT
};

//==================================================================
static void cubecross_getmatrix( int face, Matrix44 &outmat )
{
	switch ( face )
	{
	case CUBECROSS_FACE_PX:	outmat = Matrix44::RotY(  M_PI_2 );	break;
	case CUBECROSS_FACE_NX:	outmat = Matrix44::RotY( -M_PI_2 );	break;
	case CUBECROSS_FACE_PY:	outmat = Matrix44::RotX( -M_PI_2 );	break;
	case CUBECROSS_FACE_NY:	outmat = Matrix44::RotX(  M_PI_2 );	break;
	case CUBECROSS_FACE_NZ:	outmat = Matrix44::RotY(  M_PI );	break;

	default:
	case CUBECROSS_FACE_PZ:	// PZ
		outmat.Identity();
		break;
	}
}

//==================================================================
#define PI				3.14159265f

/*
major axis
direction     target                              sc     tc    ma
----------    ---------------------------------   ---    ---   ---
+rx          GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT   -rz    -ry   rx
-rx          GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT   +rz    -ry   rx
+ry          GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT   +rx    +rz   ry
-ry          GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT   +rx    -rz   ry
+rz          GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT   +rx    -ry   rz
-rz          GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT   -rx    -ry   rz

s   =   ( sc/|ma| + 1 ) / 2
t   =   ( tc/|ma| + 1 ) / 2

*/

//===============================================================================
int SHCube::FireSpecularRay_s( const Vector3 &ray, int *out_uip, int *out_vip )
{
	float	uf, vf;
	float	coe;
	int		face;
	Vector3	absray;

	absray._x = ray._x >= 0 ? ray._x : -ray._x;
	absray._y = ray._y >= 0 ? ray._y : -ray._y;
	absray._z = ray._z >= 0 ? ray._z : -ray._z;

	if ( absray._x >= absray._y && absray._x >= absray._z )	// x is major
	{
		coe = 1.0f / absray._x;
		if ( ray._x > 0 )
		{
			face = SHCUBE_FACE_PX;
			uf =  -ray._z * coe;
			vf =  -ray._y * coe;
		}
		else
		{
			face = SHCUBE_FACE_NX;
			uf =  ray._z * coe;
			vf = -ray._y * coe;
		}
	}
	else
	if ( absray._y >= absray._x && absray._y >= absray._z )	// y is major
	{
		coe = 1.0f / absray._y;
		if ( ray._y > 0 )
		{
			face = SHCUBE_FACE_PY;
			uf = ray._x * coe;
			vf = ray._z * coe;
		}
		else
		{
			face = SHCUBE_FACE_NY;
			uf =  ray._x * coe;
			vf = -ray._z * coe;
		}
	}
	else													// z is major
	{
		coe = 1.0f / absray._z;
		if ( ray._z > 0 )
		{
			face = SHCUBE_FACE_PZ;
			uf =  ray._x * coe;
			vf = -ray._y * coe;
		}
		else
		{
			face = SHCUBE_FACE_NZ;
			uf = -ray._x * coe;
			vf = -ray._y * coe;
		}
	}

	uf = uf * 0.5f + 0.5f;
	vf = vf * 0.5f + 0.5f;
	PASSERT( uf >= 0 && uf <= 1 );
	PASSERT( vf >= 0 && vf <= 1 );

	*out_uip = (int)(uf * 32768);
	*out_vip = (int)(vf * 32768);

	return face;
}

//===============================================================================
// harmonics coefficient indices
enum {
	K00 =0,
	K11 ,
	K10 ,
	K1_1,
	K21 ,
	K2_1,
	K2_2,
	K20 ,
	K22
};

//===============================================================================
SHCube::SHCube( int dim ) :
	_dim(dim)
{
	for (int i=0; i < 6; ++i)
		_Ylm_valuesp[i] = NULL;

	int			samples_per_face;
	float		dim_coe, s, t;
	Vector3		n;
	float		domega;

	samples_per_face = dim * dim;

	//------------------------------------
	for (int i=0; i < 6; ++i)
	{
		if NOT( _Ylm_valuesp[i] = (float *)PSYS_CMALLOC( samples_per_face * 9 * sizeof(float) ) )
		{
			for (int j=0; j < i; ++j)
			{
				SAFE_FREE( _Ylm_valuesp[j] );
			}
			throw;
		}
	}

	//------------------------------------
	// embed the area dividend in the coefficients so that
	// we won't have to do that when integrating
	domega = 1 / (PI*PI);//*dim*dim);
	float	sh_c1 = 0.282095f * domega;
	float	sh_c2 = 0.488603f * domega;
	float	sh_c3 = 1.092548f * domega;
	float	sh_c4 = 0.315392f * domega;
	float	sh_c5 = 0.546274f * domega;

	for (int face=0; face < 6; ++face)
	{
		float	*Ylm_valp = _Ylm_valuesp[ face ];

		Matrix44	rot;
		cubecross_getmatrix( face, rot );

		dim_coe = 2.0f / (dim-1);
		for (int j = 0; j < dim; ++j)
		{
			// flip y axis because we assume a classic bitmap with 0 at the top and height-1 at the bottom
			t = 1 - (j+0.5f) * dim_coe;
			for (int i = 0; i < dim; ++i)
			{
				s = (i+0.5f) * dim_coe - 1;

				n = rot * Vector3( s, t, 1 ).GetNormalized();

				// swizzle on 4 columns so that we can later integrate with a nice
				// matrix mul and be coherent with cache
				float *Ylm_valp_4 = Ylm_valp + (i & 3) + (i / 4) * 36;

				Ylm_valp_4[ 4 * K00 ] = sh_c1;									//-------- 00
				Ylm_valp_4[ 4 * K11 ] = sh_c2 * n._x;							//-------- 11
				Ylm_valp_4[ 4 * K10 ] = sh_c2 * n._z;							//-------- 10
				Ylm_valp_4[ 4 * K1_1] = sh_c2 * n._y;							//-------- 1_1
				Ylm_valp_4[ 4 * K21 ] = sh_c3 * n._x * n._z;					//-------- 21
				Ylm_valp_4[ 4 * K2_1] = sh_c3 * n._y * n._z;					//-------- 2_1
				Ylm_valp_4[ 4 * K2_2] = sh_c3 * n._x * n._y;					//-------- 2_2
				Ylm_valp_4[ 4 * K20 ] = sh_c4 * (3 * n._z * n._z - 1);			//-------- 20
				Ylm_valp_4[ 4 * K22 ] = sh_c5 * (n._x * n._x - n._y * n._y);	//-------- 22
			}
			Ylm_valp += dim * 9;
		}
	}
}

//-----------------------------------------
/*
--- normal table: array of 1 sample * 9 coefficients

00 01 02 03 04 05 06 07 08
10 11 12 13 14 15 16 17 18
20 21 22 23 24 25 26 27 28
30 31 32 33 34 35 36 37 38
40 41 42 43 44 45 46 47 48
50 51 52 53 54 55 56 57 58

--- fast table: block of 4 samples x 9 coefficients
--- swizzled for cache coherency

00 10 20 30
01 11 21 31
02 12 22 32
03 13 23 33
04 14 24 34
05 15 25 35
06 16 26 36
07 17 27 37
08 18 28 38

40 50 .. ..
41 51 ..
42 52
43 53
44 54
45 55
46 56
47 57
48 58
*/

//===============================================================================
SHCube::~SHCube()
{
	for (int i=0; i < 6; ++i)
		SAFE_FREE( _Ylm_valuesp[i] );
}

//===============================================================================
void SHCube::Integrate( float out_rgb_coe[3][9], ImageBase *face_maps_listp[6] )
{
	const Vector3	*rgbp;
	int				dim;
	Vector4			sum_rgb[9];
	float			*Ylm_valp;

	dim = face_maps_listp[0]->GetWidth();
	PASSERT( dim == _dim );
	PASSERT( face_maps_listp[0]->GetWidth() == face_maps_listp[0]->GetHeight() );
	PASSERT( face_maps_listp[0]->GetBaseFormat() == ImageBase::BF_R32FG32FB32F );

	for (int i=0; i < 9; ++i)
		sum_rgb[i].SetZero();

	for (int i=0; i < 6; ++i)
	{
		Ylm_valp = _Ylm_valuesp[i];

		rgbp = (const Vector3 *)face_maps_listp[i]->GetData();

		for (int y=0; y < dim; ++y)
		{
			for (int x=dim/4; x > 0; --x)
			{
				Matrix44	col_m(	Vector4( rgbp[0], 0 ),
									Vector4( rgbp[1], 0 ),
									Vector4( rgbp[2], 0 ),
									Vector4( rgbp[3], 0 ) );
				rgbp += 4;

				for (int j=0; j < 9; ++j)
				{
					Vector4	coe_vec(	Ylm_valp[0],
										Ylm_valp[1],
										Ylm_valp[2],
										Ylm_valp[3] );
					Ylm_valp += 4;

					sum_rgb[j] += col_m * coe_vec;
				}
			}
		}
	}

	for (int i=0; i < 9; ++i)
	{	// copy in output and also make the numbers "nice" for display !
		out_rgb_coe[0][i] = (int)(sum_rgb[i]._x * 100000) * (1.0f/100000);
		out_rgb_coe[1][i] = (int)(sum_rgb[i]._y * 100000) * (1.0f/100000);
		out_rgb_coe[2][i] = (int)(sum_rgb[i]._z * 100000) * (1.0f/100000);
	}
}
